package pt.compflow.tools;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import pt.compflow.flowontology.model.Event;
import pt.compflow.flowontology.model.EventInterface;
import pt.compflow.flowontology.model.FLOWO;
import pt.compflow.flowontology.model.FlowOntologyModel;
import pt.compflow.flowontology.model.Task;
import pt.compflow.flowontology.model.TaskInterface;
import pt.compflow.graph.OntModelGraphWrapper;
import pt.compflow.graph.OntModelGraphWrapperBuilder;
import pt.compflow.graph.Tree;
import pt.ipp.isep.gecad.onto2flow.model.Edge;
import pt.ipp.isep.gecad.onto2flow.model.Node;
import pt.ipp.isep.gecad.onto2flow.model.Onto2FlowModel;
import pt.ipp.isep.gecad.onto2flow.model.Onto2FlowModelFactory;
import pt.ipp.isep.gecad.onto2flow.model.Restriction;

import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;

public class OntModelHelper {
			
	protected static final String OBJECTPROPERTY_GRAPH_URI = FLOWO.getURI() + "objectPropertyGraph";
	protected static final String DATAPROPERTY_GRAPH_URI = FLOWO.getURI() + "dataPropertyGraph";
	protected static final String SUBCLASS_GRAPH_URI = FLOWO.getURI() + "subClassGraph";

	protected Onto2FlowModel flowModel;
	protected OntModel ontModel;

	protected OntModelGraphWrapper subClassGraph, objectPropertyGraph, dataPropertyGraph;
	
	public OntModelHelper(OntModel ontModel) {
		this(ontModel, false);
	}
	
	public OntModelHelper(Onto2FlowModel flowModel) {
		this(flowModel, false);
	}
	
	public OntModelHelper(OntModel ontModel, boolean onlySubClassGraph) {
		this.flowModel = Onto2FlowModelFactory.createModelForGraph(ontModel.getGraph());
		this.ontModel = ontModel;
		init(onlySubClassGraph);
	}
	
	public OntModelHelper(Onto2FlowModel flowModel, boolean onlySubClassGraph) {
		this.flowModel = flowModel;
		this.ontModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM, flowModel);	
		init(onlySubClassGraph);
	}
	
	private void init(boolean onlySubClassGraph) {
		
		OntModelGraphWrapperBuilder builder = new OntModelGraphWrapperBuilder();
		builder.setOntModel(this.ontModel);
		builder.setTargetModel(this.flowModel);
		
		if(flowModel.getGraphWithURI(SUBCLASS_GRAPH_URI) == null) {
			builder.setGraphUri(SUBCLASS_GRAPH_URI);
			builder.setType(OntModelGraphWrapperBuilder.SUBCLASSOF_GRAPH);
			this.subClassGraph = builder.build();
		} else this.subClassGraph = new OntModelGraphWrapper(flowModel, SUBCLASS_GRAPH_URI);
		
		if(!onlySubClassGraph) {
			if(flowModel.getGraphWithURI(OBJECTPROPERTY_GRAPH_URI) == null) {
				builder.setGraphUri(OBJECTPROPERTY_GRAPH_URI);
				builder.setType(OntModelGraphWrapperBuilder.OBJECTPROPERTY_GRAPH);
				this.objectPropertyGraph = builder.build();
			} else this.objectPropertyGraph = new OntModelGraphWrapper(flowModel, OBJECTPROPERTY_GRAPH_URI);
			
			if(flowModel.getGraphWithURI(DATAPROPERTY_GRAPH_URI) == null) {
				builder.setGraphUri(DATAPROPERTY_GRAPH_URI);
				builder.setType(OntModelGraphWrapperBuilder.DATATYPEPROPERTY_GRAPH);
				this.dataPropertyGraph = builder.build();
			} else this.dataPropertyGraph = new OntModelGraphWrapper(flowModel, DATAPROPERTY_GRAPH_URI);
		}
	}
	
	/**
	 * Associate all possible and available task interfaces to a specific task, according to its definition.
	 * Checks the interface class specified on the deliveredThrough property restriction of the workflow-definition ontology.
	 * 
	 * @param engineModel - the model that contains all engine interfaces
	 * @param classUri - the uri of the task class (task-definition)
	 * @param task - the task
	 * @return true if at least one interface has been associated with the task; false otherwise
	 */
	public boolean setTaskInterfacesToDeliver(FlowOntologyModel engineModel, String classUri, Task task) {
		boolean ok = false;
				
		Tree deliveredThroughTree = getApplicableEdgeDestinationsClosure(FLOWO.deliveredThrough.getURI(), classUri, FLOWO.Deliverable.getURI());

		if(deliveredThroughTree != null && !deliveredThroughTree.isEmpty()) {
			boolean isHasValue = false;
			for(Tree deliveredThroughValue : deliveredThroughTree) {
			
				if(ontModel.getResource(deliveredThroughValue.getNode()).canAs(OntClass.class)) {
					if(!isHasValue) {
						// get instances of the classes 
						StmtIterator stmtIt = engineModel.listStatements(null, RDF.type, engineModel.getResource(deliveredThroughValue.getNode()));
						while(stmtIt.hasNext()) {
							ok = true;
							task.addDeliveredThroughTaskInterface(stmtIt.next().getSubject().as(TaskInterface.class));
						}
					}
				} else {
					TaskInterface taskInterface = engineModel.getTaskInterfaceWithURI(deliveredThroughValue.getNode());
					if(taskInterface != null) {
						ok = true;
						task.addDeliveredThroughTaskInterface(taskInterface);
						isHasValue = true;
					}
				}
			}
		}
		
		return ok;
	}
	
	/**
	 * Associate all possible and available event interfaces to a specific event, according to its definition.
	 * Checks the interface class specified on the deliveredThrough property restriction of the workflow-definition ontology.
	 * 
	 * @param engineModel - the model that contains all engine interfaces
	 * @param classUri - the uri of the event class (event-definition)
	 * @param task - the event
	 * @return true if at least one interface has been associated with the event; false otherwise
	 */
	public boolean setEventInterfacesToDeliver(FlowOntologyModel engineModel, String classUri, Event event) {
		// set interfaces to deliver (get them from the engineModel)
		boolean ok = false;
		
		Tree deliveredThroughTree = getApplicableEdgeDestinationsClosure(FLOWO.deliveredThrough.getURI(), classUri, FLOWO.Deliverable.getURI());

		if(deliveredThroughTree != null && !deliveredThroughTree.isEmpty()) {
			boolean isHasValue = false;
			for(Tree deliveredThroughValue : deliveredThroughTree) {
			
				if(ontModel.getResource(deliveredThroughValue.getNode()).canAs(OntClass.class)) {
					if(!isHasValue) {
						// get instances of the classes 
						StmtIterator stmtIt = engineModel.listStatements(null, RDF.type, engineModel.getResource(deliveredThroughValue.getNode()));
						while(stmtIt.hasNext()) {
							ok = true;
							event.addDeliveredThroughEventInterface(stmtIt.next().getSubject().as(EventInterface.class));
						}
					}
				} else {
					EventInterface eventInterface = engineModel.getEventInterfaceWithURI(deliveredThroughValue.getNode());
					if(eventInterface != null) {
						ok = true;
						event.addDeliveredThroughEventInterface(eventInterface);
						isHasValue = true;
					}
				}
			}
		}
		
		return ok;
	}
	
	////////////////////////////////////////////////
	
	/**
	 * Check all property restrictions of a given class (with classUri) with the property containing the uri propertyUri.
	 * Return a tree containing all values found in these property restrictions.
	 * 
	 * If closure is true, this check will be done to the given class and all its super classes; otherwise it is done only to the given class.
	 * topSuperClassUri is only used if closure is true. It establishes that when checking super class property restrictions, if the topSuperClass is found, the algorithms will go no further. 
	 * 
	 * @param propertyUri - uri of the property for property restriction checking
	 * @param classUri - uri of the target class
	 * @param topSuperClassUri - uri of the maximum/top super class to search for property restrictions if closure is true
	 * @param closure - search through the super class tree
	 * @param hasOnly - if true, stop search when an universal property restriction is found
	 * @return a tree with all values found in the property restrictions of the given class and property
	 */
	protected Tree getApplicableEdgeDestinations(String propertyUri, String classUri, String topSuperClassUri, boolean closure, boolean hasOnly) {
		Tree result = new Tree();
		
		OntClass superClass = topSuperClassUri == null ? null : ontModel.getOntClass(topSuperClassUri);
		OntClass clazz = ontModel.getOntClass(classUri);
		OntProperty property = ontModel.getOntProperty(propertyUri);
		OntModelGraphWrapper graph = property.isObjectProperty() ? objectPropertyGraph : dataPropertyGraph;		
		
		QueryExecution qe = closure ? graph.listEdgesClosure(clazz, property, null, superClass) : graph.listEdges(clazz, property, null);
		ResultSet eIt = qe.execSelect();
		while(eIt.hasNext()) {
			Edge e = eIt.nextSolution().getResource("e").as(Edge.class);
			Node n = e.getToNodeNode();
			Restriction r = e.as(Restriction.class);
			boolean isOnly = r.getOnlyProp() != null && r.getOnlyProp();
			
			if(!hasOnly || isOnly)
				result.addChildren(n.getElement().toString());
			
			if(isOnly) hasOnly = true;
		}
		qe.close();
					
		return result;
	}
	
	/**
	 * Check all property restrictions of a given class (with classUri) with the property containing the uri propertyUri.
	 * Return a tree containing all values found in these property restrictions.
	 * 
	 * This check is not done for super classes of the given class. For such a check use getApplicableEdgeDestinationsClosure().
	 * 
	 * @param propertyUri - uri of the property for property restriction checking
	 * @param classUri - uri of the target class
	 * @return a tree with all values found in the property restrictions of the given class and property
	 */
	public Tree getApplicableEdgeDestinations(String propertyUri, String classUri) {
		Tree t = getApplicableEdgeDestinations(propertyUri, classUri, null, false, false);	
		return t;
	}
	
	/**
	 * Check all property restrictions of a given class (with classUri) with the property containing the uri propertyUri.
	 * Return a tree containing all values found in these property restrictions.
	 * 
	 * This check will be done to the given class and all its super classes.
	 * topSuperClassUri establishes that when checking super class property restrictions, if the topSuperClass is found, the algorithm will go no further. 
	 * 
	 * @param propertyUri - uri of the property for property restriction checking
	 * @param classUri - uri of the target class
	 * @param topSuperClassUri - uri of the maximum/top super class to search for property restrictions if closure is true
	 * @return a tree with all values found in the property restrictions of the given class and property
	 */
	public Tree getApplicableEdgeDestinationsClosure(String propertyUri, String classUri, String topSuperClassUri) {
		Tree t = getApplicableEdgeDestinations(propertyUri, classUri, topSuperClassUri, true, false);
		return t;
	}
	
	/**
	 * Returns a set of URI OntClasses that are direct superclasses of the focusClass. None of these super classes can't have the uri topClassUri (this is only checked if the topClassUri is not null).
	 * There can't be any subClassOf relationships between the classes in the set (thus each one of them may be seen as a leaf of the topClass tree that cuts the focusClass out).
	 * 
	 * @param focusClassUri
	 * @param topClassUri
	 * @return a list of direct super classes with no subClassOf relationships among them
	 */
	public List<OntClass> listDirectLeafSuperClasses(String focusClassUri, String topClassUri) {
		
		OntClass focusClass = ontModel.getOntClass(focusClassUri);
		OntClass topClass = topClassUri == null ? null : ontModel.getOntClass(topClassUri);
		
		List<OntClass> result = new ArrayList<OntClass>();
		if(focusClass == null) return result;
	
		Iterator<OntClass> it = focusClass.listSuperClasses();
		while(it.hasNext()) {
			OntClass r = it.next();
			
			// check if class is an URI resource and is a subclass of the topClass
			if(r.isURIResource() && (topClass == null || (r.getURI().compareToIgnoreCase(topClass.getURI()) != 0))) {// && hasSubClassClosure(ontModel, topClass.getURI(), r.getURI())))) {
				boolean ok = true;
				for(int i=0, len=result.size();i<len && ok;i++) {
					OntClass rr = result.get(i);
					if(r.getURI().compareTo(rr.getURI()) == 0 || hasSubClassClosure(r.getURI(), rr.getURI())) ok = false;					
				}
				
				// only return the class if it is not a subclass of an already present class
				// FIXME should remove classes already present if the are a subclass of this one!
				if(ok) result.add(r);
			}
		}
				
		return result;
	}
	
	
	
	/**
	 * Returns a set with all sub classes of the given class (uri) that are pure domain: they cannot be definitions of the input or output of a task.
	 * 
	 * This method is intended for the FOEXT extension only.
	 * FIXME probably should be moved to the extension project somehow.
	 * 
	 * @param classUri
	 * @return
	 */
	public Set<OntClass> listDomainSubClasses(String classUri) {
		
		OntClass clazz = ontModel.getOntClass(classUri);
		
		Set<OntClass> result = new HashSet<OntClass>();
	
		listDomainSubClasses(clazz, result);
				
		return result;
	}
	
	private void listDomainSubClasses(OntClass clazz, Set<OntClass> list) {
		Iterator<OntClass> it = clazz.listSubClasses(true);
		while(it.hasNext()) {
			OntClass r = it.next();
			
			// check if class is an URI resource and is a subclass of the topClass
			if(r.isURIResource() && !list.contains(r.getURI()) && !hasSubClassClosure("http://compflow.pt/flowOntologyExt/DomainThing", r.getURI())) {
				list.add(r);
				listDomainSubClasses(r, list);
			}
		}
	}
	
	/**
	 * Checks if the topClass contains the sub-class subClass (URI resources only). 
	 * It checks the transitive closure of the subClassOf relationship (not just the direct relationships).
	 * 
	 * @param topClassUri
	 * @param subClassUri
	 * @return true if subClassUri is subClassOf topClassUri; false otherwise
	 */
	public boolean hasSubClassClosure(String topClassUri, String subClassUri) {
		
		String queryString = "ASK { <" + subClassUri + "> <" + RDFS.subClassOf + ">+ <" + topClassUri + "> . }";
		Query query = QueryFactory.create(queryString);
		QueryExecution qexec = QueryExecutionFactory.create(query, getFlowModel());
		boolean results = qexec.execAsk();
		qexec.close();
		
		return results;
	}
	
	/**
	 * Returns the direct leaf (refer to getDirectLeafSuperClasses description for details) type of the resource res.
	 * The type must be a subclass of the topClass, otherwise null is returned.
	 *
	 * @param res - the resource
	 * @param topClassUri - top/super class of the resource type
	 * @return the first direct type of the resource found that satisfies the leaf conditions
	 */
	public Resource getDirectLeafType(Resource res, String topClassUri) {
		OntClass topClass = topClassUri == null ? null : ontModel.getOntClass(topClassUri);
		OntClass resClass = topClass;
		
		StmtIterator stmtIt = res.listProperties(RDF.type);
		while(stmtIt.hasNext()) {
			Statement stmt = stmtIt.next();
			Resource r = stmt.getObject().asResource();

			if(r.isURIResource() && (topClass == null || (r.getURI().compareToIgnoreCase(topClass.getURI()) != 0
					&& hasSubClassClosure(topClass.getURI(), r.getURI())))
					&& (resClass == null || !hasSubClassClosure(r.getURI(), resClass.getURI())))
				resClass = ontModel.getOntClass(r.getURI());
		}
		
		return topClass == null || resClass.getURI().compareTo(topClass.getURI()) != 0 ? resClass : null;
	}
	
	public Set<String> getDirectTypes(Resource res, String topClassUri, Set<String> mustHave) {
		OntClass topClass = topClassUri == null ? null : ontModel.getOntClass(topClassUri);
		Set<String> resClasses = new HashSet<String>();
		
		StmtIterator stmtIt = res.listProperties(RDF.type);
		while(stmtIt.hasNext()) {
			Statement stmt = stmtIt.next();
			Resource r = stmt.getObject().asResource();

			if(r.isURIResource() && (mustHave == null || mustHave.contains(r.getURI())) && (topClass == null || (r.getURI().compareToIgnoreCase(topClass.getURI()) != 0
					&& hasSubClassClosure(topClass.getURI(), r.getURI()))))
				resClasses.add(r.getURI());
		}
		
		return resClasses;
	}
	
	
	private void listSubPropertyClosureRec(OntProperty property, List<OntProperty> result) {
		Iterator<? extends OntProperty> propIt = property.listSubProperties(true);
		while(propIt.hasNext()) {
			OntProperty itProp = propIt.next();
			if(!result.contains(itProp)) {
				result.add(0, itProp);
				listSubPropertyClosureRec(itProp, result);
			}
		}		
	}
	
	/**
	 * Lists all subproperties of the property.
	 * It checks the transitive closure of the subPropertyOf relationship (not just the direct relationships).
	 * The property is included in the list if inclusive = true;
	 * 
	 * @param propertyUri
	 * @param inclusive
	 * @return
	 */
	public List<OntProperty> listSubPropertyClosure(String propertyUri, boolean inclusive) {
			
		OntProperty property = ontModel.getOntProperty(propertyUri);
		List<OntProperty> result = new ArrayList<OntProperty>();
		
		listSubPropertyClosureRec(property, result);
		if(inclusive && !result.contains(property)) result.add(property);
		
		return result;
	}
	
	public OntProperty getFirstApplicableSubProperty(String classUri, String propertyUri, boolean inclusive) {
			
		List<OntProperty> inputPropList = listSubPropertyClosure(propertyUri, inclusive);
		OntClass clazz = ontModel.getOntClass(classUri);
		
		for(int i=0; i<inputPropList.size(); i++) {
			OntProperty property = inputPropList.get(i);
			OntModelGraphWrapper graph = property.isObjectProperty() ? objectPropertyGraph : dataPropertyGraph;		
			
			QueryExecution qe = graph.listEdgesClosure(clazz, property, null);
			ResultSet res = qe.execSelect();
			if(res != null && res.hasNext()) {
				qe.close();
				return property;
			}
			qe.close();
		}
		
		return null;
	}
	

	public Onto2FlowModel getFlowModel() {
		return flowModel;
	}

	public void setFlowModel(Onto2FlowModel flowModel) {
		this.flowModel = flowModel;
	}

	public OntModel getOntModel() {
		return ontModel;
	}

	public void setOntModel(OntModel ontModel) {
		this.ontModel = ontModel;
	}

	public OntModelGraphWrapper getSubClassGraph() {
		return subClassGraph;
	}

	public void setSubClassGraph(OntModelGraphWrapper subClassGraph) {
		this.subClassGraph = subClassGraph;
	}

	public OntModelGraphWrapper getObjectPropertyGraph() {
		return objectPropertyGraph;
	}

	public void setObjectPropertyGraph(OntModelGraphWrapper objectPropertyGraph) {
		this.objectPropertyGraph = objectPropertyGraph;
	}

	public OntModelGraphWrapper getDataPropertyGraph() {
		return dataPropertyGraph;
	}

	public void setDataPropertyGraph(OntModelGraphWrapper dataPropertyGraph) {
		this.dataPropertyGraph = dataPropertyGraph;
	}

	public String getOntModelUri() {
		return flowModel.getResource("mainOntology").getProperty(FLOWO.hasUrl).getObject().asLiteral().getString();
	}

}
